﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Xml;
using System.Xml.Linq;
using System.Windows.Forms;

namespace SFP.Animation2D
{
    public class LayerManager
    {
        private List<Layer> layers = new List<Layer>();

        private double xScroll = 0;
        private double yScroll = 0;
        private double maxXScroll = 3200;
        private double maxYScroll = 400;

        public LayerManager() { }

        public LayerManager(List<Layer> layers)
        {
            this.layers = layers;
        }

        public void clearManager()
        {
            List<Layer> removeLayers = new List<Layer>(layers);
            foreach (Layer layer in removeLayers)
            {
                removeLayer(layer);
            }
        }

        public void addLayer(Layer layer)
        {
            layers.Add(layer);
            layer.setLayerManager(this);
            layer.scrollX(xScroll);
            layer.scrollY(yScroll);
        }

        public Layer getLayer(int index)
        {
            if (index < 0 || index >= layers.Count) return null;
            return layers[index];
        }

        public Layer getLayerByName(string name)
        {
            foreach (Layer layer in layers)
            {
                if (layer.getName().Equals(name))
                {
                    return layer;
                }
            }

            return null;
        }

        public List<Layer> getLayers()
        {
            return new List<Layer>(layers);
        }

        public void removeLayer(Layer layer)
        {
            layers.Remove(layer);
            layer.scrollX(-xScroll);
            layer.scrollY(-yScroll);
        }

        public void moveLayerBack(Layer layer)
        {
            if (!layers.Contains(layer)) return;
            int index = layers.IndexOf(layer);
            if (index == 0) return;
            Layer swapLayer = layers[index - 1];
            layers[index - 1] = layer;
            layers[index] = swapLayer;
        }

        public void moveLayerForward(Layer layer)
        {
            if (!layers.Contains(layer)) return;
            int index = layers.IndexOf(layer);
            if (index == layers.Count - 1) return;
            Layer swapLayer = layers[index + 1];
            layers[index + 1] = layer;
            layers[index] = swapLayer;
        }

        public void draw(Graphics g) {
            foreach (Layer layer in layers)
            {
                layer.draw(g);
            }
        }

        public double getXScroll()
        {
            return xScroll;
        }

        public void setXScroll(double xScroll)
        {
            scrollX(xScroll - this.xScroll);
        }

        public double getYScroll()
        {
            return yScroll;
        }

        public void setYScroll(double yScroll)
        {
            scrollY(yScroll - this.yScroll);
        }

        public double getMaxXScroll()
        {
            return maxXScroll;
        }

        public void setMaxXScroll(double maxXScroll)
        {
            if (xScroll > maxXScroll) scrollX(maxXScroll - xScroll);
            this.maxXScroll = maxXScroll;
        }

        public double getMaxYScroll()
        {
            return maxYScroll;
        }

        public void setMaxYScroll(double maxYScroll)
        {
            if (yScroll > maxYScroll) scrollY(maxYScroll - yScroll);
            this.maxYScroll = maxYScroll;
        }

        public bool isXAtBeginn()
        {
            return xScroll == 0;
        }

        public bool isXAtEnd()
        {
            return xScroll == maxXScroll;
        }

        public bool isYAtBeginn()
        {
            return yScroll == 0;
        }

        public bool isYAtEnd()
        {
            return yScroll == maxYScroll;
        }

        internal void changeZDepth(Layer layer, double zDepth)
        {
            double xScroll = this.xScroll;
            double yScroll = this.yScroll;
            scrollToOrigin();
            layer.setZDepthByManager(zDepth);
            scrollX(xScroll);
            scrollY(yScroll);
        }

        public void scrollToOrigin()
        {
            scrollX(-xScroll);
            scrollY(-yScroll);
        }

        public void scrollX(double value) {
            processScrollValue(ref value, ref xScroll, ref maxXScroll);

            foreach (Layer layer in layers)
            {
                layer.scrollX(value);
            }
        }

        public void scrollY(double value) {
            processScrollValue(ref value, ref yScroll, ref maxYScroll);

            foreach (Layer layer in layers)
            {
                layer.scrollY(value);
            }
        }

        private void processScrollValue(ref double value, ref double scroll, ref double maxScroll)
        {
            scroll += value;
            if (scroll < 0)
            {
                value -= scroll;
                scroll = 0;
            }
            else if (scroll > maxScroll)
            {
                value -= scroll - maxScroll;
                scroll = maxScroll;
            }
        }

        public void load(XDocument document, Size gameResolution, out Bitmaps bitmaps)
        {
            load(document, gameResolution, out bitmaps, false);
        }

        public void load(XDocument document, Size gameResolution, out Bitmaps bitmaps, bool resolutionOffset)
        {
            clearManager();

            bitmaps = new Bitmaps();

            XElement rootElem = document.Root;

            int resolutionWidthOffset = 0;
            int resolutionHeightOffset = 0;
            XElement resolutionElem = rootElem.Element("Resolution");
            if (resolutionElem != null)
            {
                XElement widthElem = resolutionElem.Element("Width");
                if (widthElem != null)
                {
                    int editorResolutionWidth = GetIntFromString(widthElem.Value, gameResolution.Width);
                    resolutionWidthOffset = gameResolution.Width - editorResolutionWidth;
                }
                
                XElement heightElem = resolutionElem.Element("Height");
                if (heightElem != null)
                {
                    int editorResolutionHeight = GetIntFromString(heightElem.Value, gameResolution.Height);
                    resolutionHeightOffset = gameResolution.Height - editorResolutionHeight;
                }
            }

            SetDoublePrefFromXmlElem("MaxXScroll", ref maxXScroll, 0, rootElem);
            SetDoublePrefFromXmlElem("MaxYScroll", ref maxYScroll, 0, rootElem);

            if (resolutionOffset)
            {
                maxXScroll -= resolutionWidthOffset;
                maxYScroll -= resolutionHeightOffset;
            }

            Dictionary<long, GlobalGameObjProps> globalGameObjPropsDict = new Dictionary<long, GlobalGameObjProps>();

            XElement objectsElem = rootElem.Element("Objects");
            if (objectsElem != null)
            {
                foreach (XElement objectElem in objectsElem.Elements("Object"))
                {
                    XAttribute idAttribute = objectElem.Attribute("objectID");
                    if (idAttribute != null)
                    {
                        long objectID = GetLongFromString(idAttribute.Value, 0);
                        if (globalGameObjPropsDict.ContainsKey(objectID)) continue;
                        GlobalGameObjProps globalGameObjProps = new GlobalGameObjProps();
                        Bitmap image = null;

                        foreach (XElement objectPrefElem in objectElem.Elements())
                        {
                            if ("Name".Equals(objectPrefElem.Name.LocalName))
                            {
                                globalGameObjProps.Name = objectPrefElem.Value;
                            }
                            else if ("Type".Equals(objectPrefElem.Name.LocalName))
                            {
                                globalGameObjProps.Type = objectPrefElem.Value;
                            }
                            else if ("ImgPath".Equals(objectPrefElem.Name.LocalName))
                            {
                                string relativePath = objectPrefElem.Value;
                                image = ContentLoader.LoadBitmap(relativePath);

                                if (image != null)
                                    globalGameObjProps.ImagePath = relativePath;
                            }
                        }

                        if (image != null)
                            bitmaps.AddNamedBitmap(objectID, globalGameObjProps.Name, image);

                        globalGameObjPropsDict.Add(objectID, globalGameObjProps);
                    }
                }
            }

            XElement layersElem = rootElem.Element("Layers");
            if (layersElem != null)
            {
                foreach (XElement layerElement in layersElem.Elements("Layer"))
                {
                    Layer layer = new Layer();
                    addLayer(layer);

                    foreach (XElement layerPrefElement in layerElement.Elements())
                    {
                        if ("Name".Equals(layerPrefElement.Name.LocalName))
                        {
                            layer.setName(layerPrefElement.Value);
                        }
                        else if ("ZDepth".Equals(layerPrefElement.Name.LocalName))
                        {
                            layer.setZDepth(GetDoubleFromString(layerPrefElement.Value, 0));
                        }
                        else if ("Scrollable".Equals(layerPrefElement.Name.LocalName))
                        {
                            layer.setScrollable(GetBoolFromString(layerPrefElement.Value));
                        }
                        else if ("Visible".Equals(layerPrefElement.Name.LocalName))
                        {
                            layer.setVisible(GetBoolFromString(layerPrefElement.Value));
                        }
                        else if ("GameObjects".Equals(layerPrefElement.Name.LocalName))
                        {
                            foreach (XElement gameObjElement in layerPrefElement.Elements("GameObject"))
                            {
                                XAttribute idAttribute = gameObjElement.Attribute("objectID");
                                if (idAttribute != null)
                                {
                                    long objectID = GetLongFromString(idAttribute.Value, 0);
                                    if (!globalGameObjPropsDict.ContainsKey(objectID)) continue;

                                    GlobalGameObjProps globalGameObjProps;
                                    globalGameObjPropsDict.TryGetValue(objectID, out globalGameObjProps);
                                    Bitmap image = bitmaps.GetBitmapByID(objectID);

                                    GameObject gameObj = new GameObject();
                                    gameObj.Name = globalGameObjProps.Name;
                                    gameObj.Type = globalGameObjProps.Type;
                                    gameObj.Image = image;
                                    gameObj.ImagePath = globalGameObjProps.ImagePath;

                                    layer.addLayerObject(gameObj);

                                    foreach (XElement gameObjPrefElement in gameObjElement.Elements())
                                    {
                                        if ("X".Equals(gameObjPrefElement.Name.LocalName))
                                        {
                                            gameObj.X = GetDoubleFromString(gameObjPrefElement.Value, 0);
                                        }
                                        else if ("Y".Equals(gameObjPrefElement.Name.LocalName))
                                        {
                                            gameObj.Y = GetDoubleFromString(gameObjPrefElement.Value, 0) + resolutionHeightOffset;
                                        }
                                        else if ("GroupColumns".Equals(gameObjPrefElement.Name.LocalName))
                                        {
                                            gameObj.GroupColumns = GetIntFromString(gameObjPrefElement.Value, 1);
                                        }
                                        else if ("GroupRows".Equals(gameObjPrefElement.Name.LocalName))
                                        {
                                            gameObj.GroupRows = GetIntFromString(gameObjPrefElement.Value, 1);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        private void SetDoublePrefFromXmlElem(string name, ref double pref, int defaultValue, XElement parentElem)
        {
            XElement prefElem = parentElem.Element(name);
            if (prefElem != null) pref = GetDoubleFromString(prefElem.Value, defaultValue);
            else pref = defaultValue;
        }

        private int GetIntFromString(string value, int defaultValue)
        {
            try { return int.Parse(value); }
            catch (FormatException) { return defaultValue; }
        }

        private double GetDoubleFromString(string value, double defaultValue)
        {
            try { return double.Parse(value); }
            catch (FormatException) { return defaultValue; }
        }

        private long GetLongFromString(string value, long defaultValue)
        {
            try { return long.Parse(value); }
            catch (FormatException) { return defaultValue; }
        }

        private bool GetBoolFromString(string value)
        {
            try { return bool.Parse(value); }
            catch (FormatException) { return false; }
        }

        private class GlobalGameObjProps
        {
            public string Name { set; get; }
            public string Type { set; get; }
            public string ImagePath { set; get; }

            public GlobalGameObjProps() { }
        }

        public class Bitmaps
        {
            private Dictionary<long, NamedBitmap> namedBitmapsMap;

            internal Bitmaps()
            {
                namedBitmapsMap = new Dictionary<long, NamedBitmap>();
            }

            internal void AddNamedBitmap(long id, string name, Bitmap bitmap)
            {
                NamedBitmap namedBitmap = new NamedBitmap(name, bitmap);
                namedBitmapsMap.Add(id, namedBitmap);
            }

            public Bitmap GetBitmapByID(long id)
            {
                if (namedBitmapsMap.ContainsKey(id))
                    return namedBitmapsMap[id].Bitmap;

                return null;
            }

            public Bitmap GetBitmapByName(string name)
            {
                foreach (NamedBitmap namedBitmap in namedBitmapsMap.Values)
                {
                    if (namedBitmap.Name.Equals(name))
                        return namedBitmap.Bitmap;
                }

                return null;
            }

            private class NamedBitmap
            {
                public string Name { get; private set; }
                public Bitmap Bitmap { get; private set; }

                public NamedBitmap(string name, Bitmap bitmap)
                {
                    Name = name;
                    Bitmap = bitmap;
                }
            }
        }
    }
}
